//#include <ctime>
//#include <iostream>
//#include <string>
//#include <boost/bind.hpp>
//#include <boost/shared_ptr.hpp>
//#include <boost/enable_shared_from_this.hpp>
//#include <boost/asio.hpp>
//#include <vector>
//#include "Message.hpp"
//#include "Logger.h"
//
//using boost::asio::ip::udp;
//boost::asio::io_service io_service;
//
//
//typedef std::map< int, MsgPointer > ID_MSGPOINTER;
//boost::asio::deadline_timer*		pTimeOutTimer;
//boost::asio::deadline_timer*		pPullTimer;
//ID_MSGPOINTER						m_NonAckMessages;
//
//const 	int 	TimeOutPeriod 		= 3;
//const 	int 	PullPeriod 			= 1;
//
//const 	int		TERRAINPACKETSIZE	= 256;//Make your buffer larger than
//const  	int		MsgLengh	 		= 128;
//char	buf[ TERRAINPACKETSIZE ];
//int		id = 0;
//
//
//std::string		strBuf;
//std::string		PullStr;
//std::string		clientName;
//DataType		PullType;
//Message			ReceivedMessage;
//MsgPointer		G_pullmsg;
//
//udp::endpoint sender_endpoint;
//bool EntityAcked = false;
//udp::endpoint* pReceiver_endpoint;
//udp::endpoint* pSender_endpoint;
//pthread_mutex_t non_ack_MsgsMutex ;
//
//
//
//void SetTimePullTimer();
//void SetTimeOutTimer();
//
//void LockNon_ack_MsgsMutex()
//{
//	pthread_mutex_lock(  &non_ack_MsgsMutex );
//}
//
//void UnLockNon_ack_MsgsMutex()
//{
//	pthread_mutex_unlock(  &non_ack_MsgsMutex );
//}
//
//void SendAck( Message& msg )
//{
//
//	udp::socket sock( io_service );
//	msg.m_type 		= ACK;
//	msg.m_strSenderID	= clientName;
//	std::string mm		= msg.Encode();
//	sock.connect( *pReceiver_endpoint );
//	sock.send_to( boost::asio::buffer( mm.c_str(), mm.length() ) , *pReceiver_endpoint );
//	sock.close();
//}
//
////Resending at time out always calls this function woth Waitforack = false,
//void SendData( MsgPointer Msg , udp::endpoint* pReceiver_endpoint , bool WaitForAck )
//{
//
//	if( WaitForAck )
//	{
//		LockNon_ack_MsgsMutex();
//		m_NonAckMessages.insert( std::make_pair< int , MsgPointer >( StringToInt( Msg->ID ) , Msg  ) );
//		UnLockNon_ack_MsgsMutex();
//	}
//
//	std::string mm = Msg->Encode();
//	udp::socket sock( io_service );
//	sock.connect( *(pReceiver_endpoint) );
//	sock.send_to( boost::asio::buffer( mm.c_str(), mm.length() ) , *(pReceiver_endpoint) );
//	sock.close();
//}
//void HandleTimeout(const boost::system::error_code& err )
//{
//	typedef std::map< int, MsgPointer > ID_MSGPOINTER;
//	typedef ID_MSGPOINTER::iterator MsgIterator;
//	std::vector< MsgIterator > MsgsToDelete;
//
//
//	if( err !=  boost::asio::error::operation_aborted )
//	{
//		LockNon_ack_MsgsMutex();
//		MsgIterator Msg ;
//		if( m_NonAckMessages.size() > 0 )
//		{
//			Msg = m_NonAckMessages.begin() ;
//			for( ; Msg != m_NonAckMessages.end() ; Msg++ )
//				{
//					if( Msg->second->IsTimedOut( TimeOutPeriod ) )
//						{
//							if( Msg->second->m_bAcknowledged == false )
//							{
//								SendData( Msg->second , pReceiver_endpoint, false );
//							}
//							else
//							{
//								MsgsToDelete.push_back( Msg );
//							}
//						}
//				}
//
//				//Now delete all the acknowledged messages from the list.
//				std::vector< MsgIterator >::iterator VecMessages = MsgsToDelete.begin();
//				for( ; VecMessages < MsgsToDelete.end() ; VecMessages++ )
//				{
//					m_NonAckMessages.erase( *VecMessages  );
//				}
//				MsgsToDelete.clear();
//		}
//		UnLockNon_ack_MsgsMutex();
//	}
//	else
//	{
//		std::cout<<"Sever::HandleTimeout Operation Interrupted  \n";
//
//	}
//	SetTimeOutTimer();
//
//}
//
//
//void HandlePull(const boost::system::error_code& err )
//{
//	if( err !=  boost::asio::error::operation_aborted )
//	{
//
//		std::cout<<"SetTimePullTimer\n";
//		MsgPointer pullmsg( new Message() );
//		pullmsg->ID 		= IntToString( id++ );
//		pullmsg->m_type		= REQ;
//		pullmsg->m_DataType	= Temp;
//		pullmsg->m_action	= PULL;
//		pullmsg->data		= PullType;
//		pullmsg->m_strSenderID	= clientName;
//		G_pullmsg				= pullmsg;
//		PullStr = pullmsg->Encode();
//
//		//Send the first pull message.
//		time( &pullmsg->m_SendTime );
//		SendData( pullmsg, pReceiver_endpoint , true  );
//		std::cout<<"Client Sent pull request: "<< PullStr <<"\n";
//	}
//
//	SetTimePullTimer();
//}
//
//void SetTimeOutTimer()
//{
//	std::cout<<"SetTimeOutTimer\n";
//	pTimeOutTimer->expires_from_now( boost::posix_time::seconds( TimeOutPeriod ) );
//	pTimeOutTimer->async_wait( boost::bind( &HandleTimeout , boost::asio::placeholders::error ) );
//}
//void SetTimePullTimer()
//{
//
//	std::cout<<"SetTimePullTimer";
//	pPullTimer->expires_from_now( boost::posix_time::seconds( PullPeriod ) );
//	pPullTimer->async_wait( boost::bind( &HandlePull , boost::asio::placeholders::error ) );
//}
//
//void start_receive(  udp::endpoint  , udp::socket );
//
//
//void handle_receive(const boost::system::error_code& error,
//     std::size_t /*bytes_transferred*/)
// {
//   if (!error || error == boost::asio::error::message_size)
//   {
//
//
//	   start_receive();
//
//   }
// }
//
//
//void start_receive(  udp::endpoint remote_endpoint_ , udp::socket socket_ )
//{
//  memset( buf, 0, TERRAINPACKETSIZE );
//
//  socket_.async_receive_from(
//	boost::asio::buffer( buf, MsgLengh ), remote_endpoint_,
//	boost::bind( handle_receive,
//	boost::asio::placeholders::error,
//	boost::asio::placeholders::bytes_transferred));
//}
//
//int main(int argc, char* argv[])
//{
//  try
//  {
//    if (argc != 7 )
//    {
//      std::cerr << "Usage: client <host> <port> <own port> <ClientName> <DataType? 0: Temperature, 1: traffic > <interactive> 0 no :1 yes>" << std::endl;
//      return 1;
//    }
//    clientName	= argv[ 4 ];
//    PullType	= (DataType)atoi(  argv[ 5 ] );
//
//    Logger LOGGER( std::cout, true , false );
//
//
//    pthread_mutex_init( &non_ack_MsgsMutex , NULL );
//
//	udp::endpoint receiver_endpoint( boost::asio::ip::address::from_string( argv[ 1 ]) , atoi( argv[ 2 ] ));
//	pReceiver_endpoint = &(receiver_endpoint);
//
//	udp::socket socket(io_service);
//	socket.open(udp::v4());
//
//	udp::endpoint local_endpoint( boost::asio::ip::address::from_string( "127.0.0.1" ), atoi(argv[3]) );
//	socket.bind( local_endpoint );
//
//	pTimeOutTimer	= new boost::asio::deadline_timer( io_service );
//
//	MsgPointer EntityMsg( new Message() );
//	EntityMsg->ID 		= IntToString( id++ );
//	EntityMsg->m_type		= REQ;
//	EntityMsg->m_DataType	= CSS;
//	EntityMsg->m_action	= ENTITY;
//	EntityMsg->data		= "Client";
//	EntityMsg->m_strSenderID = clientName;
//
//	std::string strEntityMsg =  EntityMsg->Encode();
//
//	//Wait till Ack.
//	while( !EntityAcked )
//	{
//		socket.send_to( boost::asio::buffer( strEntityMsg.c_str(), strEntityMsg.length() ), receiver_endpoint);
//		size_t len = socket.receive_from( boost::asio::buffer( buf, 128 ), sender_endpoint );
//		std::cout<<"Client received "<<len<<"bytes "<< buf<<"\n";
//		strBuf = buf;
//		ReceivedMessage.Decode( strBuf );
//		if( ACK == ReceivedMessage.m_type )
//		{
//			if( ENTITY == ReceivedMessage.m_action )
//			{
//				std::cout<<"Client Acknowledged \n";
//				EntityAcked = true;
//				break ;
//			}
//		}
//	}
//
//	std::cout<<"After while \n";
//	pPullTimer		= new boost::asio::deadline_timer( io_service );
//	std::cout<<"After new boost::asio::deadline_timer( io_service ); \n";
//
//
//	SetTimeOutTimer();
//	SetTimePullTimer();
//
//	while( true )
//    {
//    	LOGGER<<" Before receiving \n";
//    	size_t len = socket.receive_from( boost::asio::buffer( buf, 128 ), receiver_endpoint );
//    	std::cout<<"Client received "<<len<<"bytes "<< buf<<"\n";
//    	strBuf = buf;
//    	ReceivedMessage.Decode( strBuf );
//
//    	if( ReceivedMessage.m_action == PUSH  )
//    	{
//    		//Ack Message.
//    		SendAck( ReceivedMessage );
//    	}
//    }
//	io_service.run();
//
//}
//  catch (std::exception& e)
//  {
//    std::cerr << e.what() << std::endl;
//  }
//
//  pthread_mutex_destroy( &non_ack_MsgsMutex  );
//  return 0;
//}




#include <ctime>
#include <iostream>
#include <string>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
#include <vector>
#include "Message.hpp"
#include "Logger.h"

using boost::asio::ip::udp;
boost::asio::io_service io_service;
const   int     TimeOutPeriod           = 3;
const   int             TERRAINPACKETSIZE       = 256;//Make your buffer larger than
const   int             MsgLengh                        = 128;
char    buf[ TERRAINPACKETSIZE ];
int             id = 0;
std::string             strBuf;
std::string             PullStr;
std::string             clientName;
Message                 ReceivedMessage;
MsgPointer              G_pullmsg;

udp::endpoint sender_endpoint;
bool EntityAcked = false;
udp::endpoint* pReceiver_endpoint;
udp::endpoint* pSender_endpoint;

//TODO If I have time , I should make this faster.
void SendAck( Message& msg )
{
        udp::socket sock( io_service );
        msg.m_type           = ACK;
        msg.m_strSenderID       = clientName;
        std::string mm          = msg.Encode();
        sock.connect( *pReceiver_endpoint );
        sock.send_to( boost::asio::buffer( mm.c_str(), mm.length() ) , *pReceiver_endpoint );
        std::cout<<"Sending Acknowledgement \n";
        std::cout<<msg.Encode()<<"\n";
        sock.close();
}



int main(int argc, char* argv[])
{
  try
  {
    if (argc != 8 )
    {
    	std::cerr << "Usage: client <host> <port> <own port> <ClientName> <DataType? 0: Temperature, 1: traffic > <NoAck> 0 no :1 yes> <CrazyMsg> 0 no 1 yes" << std::endl;
      return 1;
    }

    Logger LOGGER( std::cout, true , false );

        udp::endpoint receiver_endpoint( boost::asio::ip::address::from_string( argv[ 1 ]) ,
                                                                 atoi( argv[ 2 ] ));

        pReceiver_endpoint = &(receiver_endpoint);

        udp::socket socket(io_service);
        socket.open(udp::v4());

        udp::endpoint local_endpoint( boost::asio::ip::address::from_string( "127.0.0.1" ), atoi(argv[3]) );
        socket.bind( local_endpoint );

        bool NoAck		=	bool( atoi( argv[ 6 ] ) );
        bool CrazyMsg	=	bool( atoi( argv[ 7 ] ) );

        clientName = argv[ 4 ];

        MsgPointer msg( new Message() );
        msg->ID                 = IntToString( id++ );
        msg->m_type  = REQ;
        msg->m_DataType = CSS;
        msg->m_action   = ENTITY;
        msg->data               = "Client";
        msg->m_strSenderID = clientName;

        std::string strEntityMsg =  msg->Encode();

        MsgPointer pullmsg( new Message() );
        pullmsg->ID             = IntToString( id++ );
        pullmsg->m_type      = REQ;
        pullmsg->m_DataType     = Temp;
        pullmsg->m_action       = PULL;
        pullmsg->data           = "GiveMeData";
        pullmsg->m_strSenderID  = clientName;
        G_pullmsg                               = pullmsg;
        PullStr = pullmsg->Encode();

        std::cout<<"sening Entity \n";

        while( !EntityAcked )
        {
                socket.send_to( boost::asio::buffer( strEntityMsg.c_str(), strEntityMsg.length() ), receiver_endpoint);
                size_t len = socket.receive_from( boost::asio::buffer( buf, 128 ), sender_endpoint );
                std::cout<<"Client received "<<len<<"bytes "<< buf<<"\n";
                strBuf = buf;
                ReceivedMessage.Decode( strBuf );
                if( ACK == ReceivedMessage.m_type )
                {
                        if( ENTITY == ReceivedMessage.m_action )
                        {
                                std::cout<<"Client Acknowledged \n";
                                EntityAcked = true;
                        }
                }
        }

        //Send the first pull message.
        time( &pullmsg->m_SendTime );
        socket.send_to( boost::asio::buffer( PullStr.c_str(), PullStr.length() ), receiver_endpoint);

    while( true )
    {
        size_t len = socket.receive_from( boost::asio::buffer( buf, 128 ), sender_endpoint );
        std::cout<<"Client received "<<len<<"bytes "<< buf<<"\n";
        strBuf = buf;
        ReceivedMessage.Decode( strBuf );
        if( ReceivedMessage.m_action == SUBMIT )
        {
                time( &pullmsg->m_ackTime );
                pullmsg->m_bAcknowledged = true;
                LOGGER<<"Time between Pull message with ID"<<pullmsg->ID<<" and its response(approximate) = "<<( difftime( pullmsg->m_ackTime , pullmsg->m_SendTime ) )<<" seconds \n";
        }
        if( ReceivedMessage.m_action == PUSH  )
        {
                //Ack Message.
        		//If not running in crazy mode, send  acknowlegement message.
				if( ! NoAck )
				{
					SendAck( ReceivedMessage );
				}
        }
        if( pullmsg->IsTimedOut( TimeOutPeriod ) )
        {
                LOGGER<<"Pull Message"<<pullmsg->ID<<"Time out, resending\n";
                socket.send_to( boost::asio::buffer( PullStr.c_str(), PullStr.length() ), receiver_endpoint);
                pullmsg->UpdateResend();
                //Don't send new message
                continue;
        }

        sleep( rand()%8 );

        time( &pullmsg->m_SendTime );
        pullmsg->ID = IntToString( id++ );
        PullStr		= pullmsg->Encode();
        pullmsg->ResetResend();

        std::cout<<"Sending pull request: "<< PullStr <<"\n";
        socket.send_to( boost::asio::buffer( PullStr.c_str(), PullStr.length() ), receiver_endpoint );

        sleep( 1 );
        if( CrazyMsg )//Send  a meaningless message to the server.
        {
        	MsgPointer crazyMessage( new Message() );
        	crazyMessage->m_type	= ACK;
        	crazyMessage->m_action	= SUBMIT;
        	crazyMessage->ID		= "12345678";
        	crazyMessage->m_strSenderID		= clientName;
        	crazyMessage->data				= "Crazy Message";
            std::string CrazyStr	= crazyMessage->Encode();
        	std::cout<<"Sending Crazy message: "<< CrazyStr <<"\n";
        	socket.send_to( boost::asio::buffer( CrazyStr.c_str(), CrazyStr.length() ), receiver_endpoint );
        }

    }

}
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }

  return 0;
}
